
/*
Copyright (c) 2014, ebaklund
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
   this list of conditions and the following disclaimer in the documentation 
   and/or other materials provided with the distribution.

3. Neither the name of the <ORGANIZATION> nor the names of its contributors 
   may be used to endorse or promote products derived from this software 
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/


#include <Do\Flow\Nodes\RegularTimerFlowNode.h>
#include <Do\Flow\Nodes\StopWatchFlowNode.h>

#include <Windows.h>
#include <stdint.h>

#pragma region INTERNAL RegularTimerFlowNode::Impl

class Do::Flow::Nodes::RegularTimerFlowNode::Impl : public FlowPropertyOwner
{
private:

  struct InternalState
    {
      ULONGLONG interval_tick_count_64;
      ULONGLONG last_tick_count_64;

      mutable double tick_lapse;
      mutable double tick_lapse_n;

      InternalState()
      : interval_tick_count_64(std::numeric_limits<ULONGLONG>::max()),
        last_tick_count_64(0),
        tick_lapse(0.0),
        tick_lapse_n(0.0)
      {
      }
    } _ ;

  bool isRunning() const
    {
      bool is_running = in.state() == RegularTimerFlowNode::RUNNING;
      return is_running;
    }

  bool isStopped() const
    {
      bool is_stopped = in.state() == RegularTimerFlowNode::RUNNING;
      return is_stopped;
    }

  static 
  ULONGLONG getFlooredTickCount64(ULONGLONG interval)
    {
      ULONGLONG flored_tick_count = interval * (GetTickCount64()/interval);
      return flored_tick_count;
    }

  static 
  bool isLapseTimeout(ULONGLONG tick_interval, ULONGLONG last_tick)
    {
      ULONGLONG current_lapse = GetTickCount64() - last_tick;
      bool is_lapse_timeout = current_lapse > tick_interval;
      return is_lapse_timeout;
    }

  static 
  ULONGLONG asTicks(RegularTimerFlowNode::Seconds seconds)
    {
      ULONGLONG as_ticks = static_cast<ULONGLONG>(seconds) * 1000;
      return as_ticks;
    }

  static 
  double asSeconds(ULONGLONG ticks_64)
    {
      double as_seconds = static_cast<double>(ticks_64) / 1000.0;
      return as_seconds;
    }

  ULONGLONG getTickLapse64() const
    {
      ULONGLONG tick_lapse_64 = _.interval_tick_count_64 + GetTickCount64() - _.last_tick_count_64; 
      return tick_lapse_64;
    }

  double getTickLapseSeconds() const
    {
      double tick_lapse_seconds = asSeconds(getTickLapse64());
      return tick_lapse_seconds;
    }

  void run()
    {
      assert(isRunning());

      _.interval_tick_count_64 = asTicks(in.tickInterval());
      _.last_tick_count_64     = getFlooredTickCount64(_.interval_tick_count_64);
    }

  void stop()
    {
      assert(isStopped());

      _.interval_tick_count_64 = std::numeric_limits<ULONGLONG>::max();
      _.last_tick_count_64     = 0;
    }

public:
  Impl() 
    {
      quietlySet(in.state, STOPPED);
      quietlySet(in.tickInterval, 0.05 * 1000); // 20 Hz
    
      ctl.drive             >>= [&] () { handleDriveCall(); };
      ctl.isEnabled         >>= [&] () { handleEnableCall(); };
      in.state              >>= [&] () { handleStateChange(); };
      in.tickInterval       >>= [&] () { handleIntervalChange(); };
      in.tickFrequence      >>= [&] () { handleFrequencyChange(); };

    //    out.tickLapseSeconds  >>= [&] () { return evaluateTickLapseSeconds(*this); };
    //    out.tickLapseNormed   >>= [&] () { return evaluateTickLapseNormed(*this); };
    }

  void handleDriveCall()
    {
      bool should_tick = 
        isRunning() && 
        isLapseTimeout(_.interval_tick_count_64, _.last_tick_count_64);

      if (!should_tick)
        return;

      _.last_tick_count_64 = getFlooredTickCount64(_.interval_tick_count_64);

      out.tick.push();
      taint(out.tickLapseSeconds);
      taint(out.tickLapseNormed);
    }

  void handleEnableCall()
    {
    }

  void handleStateChange()
    {
      switch(in.state())
      {
      case RegularTimerFlowNode::RUNNING : run(); break;
      case RegularTimerFlowNode::STOPPED : stop(); break;
      }
    }

  void handleIntervalChange()
    {
      double frequence = 1.0 / in.tickInterval();
      quietlySet(in.tickFrequence, frequence);

      _.interval_tick_count_64 = asTicks(in.tickInterval());
      
      handleDriveCall();
    }

  void handleFrequencyChange()
    {
      double interval = 1.0 / in.tickFrequence();
      quietlySet(in.tickInterval, interval);

      _.interval_tick_count_64 = asTicks(interval);
      
      handleDriveCall();
    }

  double const & evaluateTickLapseSeconds() const
    {
      if (in.state() == RegularTimerFlowNode::STOPPED)
        _.tick_lapse = 0.0;
      else
        _.tick_lapse = getTickLapseSeconds(); 

      return _.tick_lapse;
    }

  double const & evaluateTickLapseNormed() const
    {
      if (in.state() == RegularTimerFlowNode::STOPPED)
        _.tick_lapse_n = 0.0;
      else
        _.tick_lapse_n = getTickLapseSeconds() / in.tickInterval();

      return _.tick_lapse_n;
    }
 
  struct
    {
      EagerSlotImpl<void>     drive;
      SinkProperty<bool>      isEnabled;
    } ctl;

  struct
    {
      SinkProperty<State>     state;
      SinkProperty<Frequence> tickFrequence;
      SinkProperty<Seconds>   tickInterval;
    } in;

  struct 
    {
      EagerSignalImpl<void>   tick;
      SourceProperty<Seconds> tickLapseSeconds;
      SourceProperty<double>  tickLapseNormed;
    } out;
};

#pragma endregion

#pragma region INTERNAL RegularTimerFlowNode::Ctl

Do::Flow::Nodes::RegularTimerFlowNode::Ctl::Ctl(Impl* _)
  : drive(_->ctl.drive),
    isEnabled(_->ctl.isEnabled)
  {}

#pragma endregion

#pragma region INTERNAL RegularTimerFlowNode::In 

Do::Flow::Nodes::RegularTimerFlowNode::In::In(Impl* _)
  : state(_->in.state),
    tickFrequence(_->in.tickFrequence),
    tickInterval(_->in.tickInterval)
  {}

#pragma endregion

#pragma region INTERNAL RegularTimerFlowNode::Out                  

Do::Flow::Nodes::RegularTimerFlowNode::Out::Out(Impl* _)
  : tick(_->out.tick),
    tickLapseSeconds(_->out.tickLapseSeconds),
    tickLapseNormed(_->out.tickLapseNormed)
  {}

#pragma endregion

#pragma region API RegularTimerFlowNode                 

Do::Flow::Nodes::RegularTimerFlowNode::RegularTimerFlowNode()
  : _(new Impl),
    ctl(_),
    in(_),
    out(_)
  {}

Do::Flow::Nodes::RegularTimerFlowNode::~RegularTimerFlowNode()
{
  delete _;
  _ = nullptr;
}

#pragma endregion
